package org.texteasy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import org.texteasy.BulkMessenger.GroupListItem;

import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.util.Pair;

public class ContactsAccessorSdk5 extends ContactsAccessor {

	
	private String[] phonesProjection = new String[]{Phone.RAW_CONTACT_ID , 
		Phone.DISPLAY_NAME, Phone.TYPE, Phone.NUMBER, Phone._ID};
	
	private String[] groupsProjection = new String[]{Groups._ID, Groups.SYSTEM_ID, Groups.TITLE, 
			Groups.ACCOUNT_NAME, Groups.ACCOUNT_TYPE};
	
	private static String groupMembershipWhereClause = null;
	
	@Override
	public Cursor queryContacts(ContentResolver contentResolver, String filter, long selectedGroupId,
			ArrayList<Integer> phoneTypes, boolean primaryNumbersOnly) {
		
		if(filter == null || "".equals(filter.trim())){
			filter = "%";
		}else{
			filter = filter + "%";
		}
		
		if(selectedGroupId >= 0){
			
			if(groupMembershipWhereClause == null){
				groupMembershipWhereClause = generateGroupMembershipWhereClause(contentResolver);
			}
			
			//using "hidden" information by referring to table: view_data_restricted. Vulnerable to breakage in future 
			//version of android!
			StringBuilder whereClause = new StringBuilder(Phone.DISPLAY_NAME).
				append(" like '").
				append(filter).
				append("' AND ").
				append(Phone.RAW_CONTACT_ID).
				append(" in (").
				append(groupMembershipWhereClause).
				append(")");
			
			filterPhoneTypes(phoneTypes, whereClause);
			
			if(primaryNumbersOnly){
				whereClause.append(" AND ").
					append(Phone.IS_PRIMARY).
					append("= 1");
			}
			return contentResolver.query(Phone.CONTENT_URI, phonesProjection, 
					whereClause.toString(), new String[]{selectedGroupId + ""}, Phone.DISPLAY_NAME);
		}else{
			StringBuilder whereClause = new StringBuilder(Phone.DISPLAY_NAME).
				append(" like '").
				append(filter).
				append("' ");
			
			filterPhoneTypes(phoneTypes, whereClause);
			if(primaryNumbersOnly){
				whereClause.append(" AND ").
					append(Phone.IS_PRIMARY).
					append("= 1");
			}
			return contentResolver.query(Phone.CONTENT_URI, phonesProjection, 
				whereClause.toString(), null, Phone.DISPLAY_NAME);
		}
	}
	
	private String generateGroupMembershipWhereClause(ContentResolver contentResolver){
		
		//Can't know exactly what a query needs to look like as each version
		//of android can change it, but we do know what is supported so write a 
		//contentResolver.query but put an invalid token in to cause an exception
		//then parse the query out. 
		//Note this is a hack subject to break if the query isn't printed in the 
		//exception
		StringBuilder whereClause = new StringBuilder(GroupMembership.MIMETYPE).
			append(" =  '").
			append(GroupMembership.CONTENT_ITEM_TYPE).
			append("'  and ").
			append(GroupMembership.GROUP_ROW_ID).
			append(" = ?");  
		
		String toReturn = null;
		try{
			
			contentResolver.query(Data.CONTENT_URI, new String[]{GroupMembership.RAW_CONTACT_ID}, 
				whereClause.toString(), null, "INVALID");//the INVALID token will be stripped from the sql in the exception
			
		}catch(Exception ex){
			
			String exception = ex.getMessage();
			int start = exception.toUpperCase().indexOf("SELECT ");
			int end = exception.toUpperCase().indexOf("ORDER BY INVALID");
			toReturn = exception.substring(start, end);
		}
		
		return toReturn;
	}
	
	private void filterPhoneTypes(ArrayList<Integer> phoneTypes, StringBuilder whereClause) {
		//get the phoneTypes to filter
		//There are a lot more Phone types than we show on the filter
		//The ones not shows are going to be associated with PhoneTypeOther
		//When making the query if PhoneTypeOther is selected then include
		//all PhoneTypes except those unselected in the Preferences (i.e.
		//if Other is selected and all the rest in prefs are unselected then
		//put in where clause: where Phone.Type not in (PhoneType.Mobile, PhoneType.Home...)
		//If Other and Mobile is selected and the rest are unselected then
		//where becomes: where Phone.Type not in (PhoneType.Home...))
		//If Other is not selected then where clause becomes a simple "in" affair where
		//all selected types are in the "in" statement
		//(i.e. where Phone.Type in (PhoneType.Mobile, PhoneType.Home...) )
		//If all types are selected then don't add anything to the where clause
		boolean[] selected = new boolean[]{false,false,false,false,false};
		for(int phoneType: phoneTypes){
			if(phoneType == Phone.TYPE_OTHER){
				selected[0] = true;
			}
			if(phoneType == Phone.TYPE_MOBILE){
				selected[1] = true;
			}
			if(phoneType == Phone.TYPE_CUSTOM){
				selected[2] = true;
			}
			if(phoneType == Phone.TYPE_HOME){
				selected[3] = true;
			}
			if(phoneType == Phone.TYPE_WORK){
				selected[4] = true;
			}
		}
		
		boolean allSelected = true;
		for(boolean selectedType: selected){
			if(!selectedType){
				allSelected = false;
			}
		}
		
		if(allSelected){
			//All are selected
		}else if(selected[0]){
			
			//Other is selected
			whereClause.append(" AND ").
				append(Phone.TYPE).
				append(" NOT IN (").
				append(buildInStatement(selected, true)).
				append(")");
		}else{
			//Other is not selected
			whereClause.append(" AND ").
			append(Phone.TYPE).
			append(" IN (").
			append(buildInStatement(selected, false)).
			append(")");
		}
	}

	private String buildInStatement(final boolean[] origSelected, 
		final boolean reverseSelections) {
		
		boolean[] selected = origSelected;
		if(reverseSelections){
			for(int i = 0; i < selected.length ;i++){
				selected[i] = !selected[i];
			}
		}
		
		StringBuilder toReturn = new StringBuilder();
		if(selected[1]){
			toReturn.append(Phone.TYPE_MOBILE).
				append(",");
		}
		
		if(selected[2]){
			toReturn.append(Phone.TYPE_CUSTOM).
				append(",");
		}
		
		if(selected[3]){
			toReturn.append(Phone.TYPE_HOME).
				append(",");
		}
		
		if(selected[4]){
			toReturn.append(Phone.TYPE_WORK).
				append(",");
		}
		
		if(toReturn.length() == 0){
			return "";
		}else{
			return toReturn.substring(0, toReturn.length() -1 );
		}
	}
	
	@Override
	public ArrayList<GroupListItem> queryGroups(Context context,ContentResolver contentResolver) {
		
		/*
		 * Example code to insert test group
		 * ContentValues values = new ContentValues();
    	values.put(android.provider.ContactsContract.Groups.TITLE, "MyGroup");
    	contentResolver.insert(android.provider.ContactsContract.Groups.CONTENT_URI, values);
    	*/
    	
    	/*
    	 * Example code to insert test group membership associations
    	 * ContentValues values = new ContentValues();
    	values.put(android.provider.ContactsContract.CommonDataKinds.GroupMembership.RAW_CONTACT_ID, 1);
    	values.put(android.provider.ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE, 
    			android.provider.ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
    	values.put(android.provider.ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, 1);
    	contentResolver.insert(android.provider.ContactsContract.Data.CONTENT_URI, values); 
    	
    	values = new ContentValues();
    	values.put(android.provider.ContactsContract.CommonDataKinds.GroupMembership.RAW_CONTACT_ID, 2);
    	values.put(android.provider.ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE, 
    			android.provider.ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
    	values.put(android.provider.ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, 1);
    	contentResolver.insert(android.provider.ContactsContract.Data.CONTENT_URI, values);
    	*/
		
    	/* Used this to figure out how to query groupMembership. If you put some invalid value in
    	 * where clause and get android to throw an exception you can see the query used
    	 * Cursor tempCursor = contentResolver.query(android.provider.ContactsContract.Data.CONTENT_URI, 
			new String[]{GroupMembership.GROUP_ROW_ID,GroupMembership.GROUP_SOURCE_ID, 
			GroupMembership.DISPLAY_NAME, GroupMembership.RAW_CONTACT_ID}, 
			GroupMembership.MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE + "'", null,null);
    	
    	while(tempCursor.moveToNext()){
    		String groupRowId = tempCursor.getString(0);
    		String groupSourceId = tempCursor.getString(1);
    		String displayName = tempCursor.getString(2);
    		Long contactId = tempCursor.getLong(3);
    		Log.e("ContactsAccessor", "groupRowId: " + groupRowId + " groupSourceId: " + groupSourceId + 
				" displayName: " + displayName + " contactId: " + contactId);
    	}*/
    	
		//first get account info
		final AccountManager am =  AccountManager.get(context.getApplicationContext());
		HashMap<String, Pair<String, Integer>> accountTypeToLabel = Account.getAccountTypeLabelMap(
				context, am);
		
		ArrayList<GroupListItem> groups = new ArrayList<GroupListItem>();
		groups.add(new GroupListItem(-1l, context.getString(R.string.ALL_CONTACTS_WITH_PHONES), 
				null, null));
		
		Cursor cursor = contentResolver.query(Groups.CONTENT_URI, groupsProjection, 
				null,null,Groups.ACCOUNT_NAME + "," + Groups.TITLE);
		
		while (cursor.moveToNext()) {
			String systemId = cursor.getString(BulkMessenger.GROUPS_COLUMN_INDEX_SYSTEM_ID);
			String name = cursor.getString(BulkMessenger.GROUPS_COLUMN_INDEX_NAME);
			Long groupId = cursor.getLong(BulkMessenger.GROUPS_COLUMN_INDEX_ID);
			String accountName = cursor.getString(BulkMessenger.GROUPS_COLUMN_ACCOUNT_NAME);
			String accountType = cursor.getString(BulkMessenger.GROUPS_COLUMN_ACCOUNT_TYPE);
			
			//extract the account package name to label (i.e. com.android.exchange -> Microsoft Exchange ActiveSync)
			//else just print out the package name
			Pair<String, Integer> authInfo = accountTypeToLabel.get(accountType);
			if(authInfo != null){
				accountType = authInfo.first;
			}
			
			groups.add(new GroupListItem(groupId, name, accountName, accountType));
		}
		
		return groups;
	}
	
	@Override
	public Cursor queryContactsForPhoneNumbers(ContentResolver contentResolver,
			HashSet<Long> selectedIds) {
		
		StringBuffer buffer = new StringBuffer(" in(");
    	for(Long selectedId: selectedIds){
    		buffer.append(selectedId).
				append(","); 
    	}
		
    	String inStatement = buffer.substring(0, buffer.length() - 1) + ")";
    	
    	//Log.e(TAG, "before query createNewMessage");
    	
    	String[] projection = new String[] {Phone.RAW_CONTACT_ID, Phone.NUMBER, Phone._ID};
        
        Uri contacts = Phone.CONTENT_URI;
        
        return contentResolver.query(contacts, projection, 
    		Phone.RAW_CONTACT_ID + inStatement, null, null);
	}
	
	@Override
	public String getPhoneColumnIndex() {
		
		return Phone.NUMBER;
	}
	
	@Override
	public int getPhoneTypeCustom() {
		return CommonDataKinds.Phone.TYPE_CUSTOM;
	}
	
	@Override
	public int getPhoneTypeHome() {
		return CommonDataKinds.Phone.TYPE_HOME;
	}
}
